﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.IO;
using System.Timers;
using BTalk.Crypto;
using System.Xml.Linq;
using BTalk.Crypto.Algorithms;

namespace BTalk.Network
{
    public delegate void Message(string message);
    public delegate void FileMessage(byte[] fileData, string fileName);
    public delegate void AlgorithmRecognized(byte[] configurationData, string algorithmName);

    public class NetworkManager : INetworkManager
    {
        private const char messagePrefix = 'm';
        private const char filePrefix = 'f';
        private const char configurationPrefix = 'c';
        private bool sessioninitialized = false;

        private TcpListener Listener { get; set; }
        private TcpClient Client { get; set; }
        private IList<Socket> ServerSockets { get; set; }
        private System.Timers.Timer Timer { get; set; }
        private ICrypto CryptoSystem { get; set; }

        public event Message MessageReceived;
        public event FileMessage FileReceived;
        public event AlgorithmRecognized AlgorithmNegotiated;

        public ICryptoStrategy GetCipher()
        {
            return CryptoSystem.Algorithm;
        }

        public NetworkManager()
        {
            CryptoSystem = new Crypto.Crypto();

            Timer = new System.Timers.Timer(100);
            Timer.Elapsed += Timer_Elapsed;
            Timer.AutoReset = false;
            Timer.Enabled = true;
            try
            {

                ServerSockets = new List<Socket>();
                Listener = new TcpListener(IPAddress.Any, 5001);
                Listener.Start();

                Listener.BeginAcceptSocket(new AsyncCallback(AcceptConnection), Listener);
            }
            catch (Exception)
            {
            }
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (Client != null)
            {
                HandleMessage(Client.GetStream());
            }
            else
            {
                foreach (var socket in ServerSockets)
                {
                    HandleMessage(new NetworkStream(socket));
                }
            }

            Timer.Start();
        }

        private void AcceptConnection(IAsyncResult ar)
        {
            TcpListener listener = (TcpListener)ar.AsyncState;
            Socket clientSocket = listener.EndAcceptSocket(ar);
            ServerSockets.Add(clientSocket);
        }

        public void Connect(string ipAddress, int port)
        {
            Client = new TcpClient();
            Client.BeginConnect(IPAddress.Parse(ipAddress), 5001, new AsyncCallback(ClientConnected), Client);
        }

        private void ClientConnected(IAsyncResult ar)
        {
            var socket = (TcpClient)ar.AsyncState;
            socket.EndConnect(ar);
        }

        public void SendMessage(string message)
        {
            using (var timer = new BTalk.Timer.DisposableTimer("Sending message"))
            {
                var msg = BitConverter.GetBytes(messagePrefix).Concat(CryptoSystem.Encode(UTF8Encoding.UTF8.GetBytes(message)).ToArray()).ToArray();
                SendMessage(msg);
            }
        }

        private void SendConfigurationData(byte[] configuration)
        {
            using (var timer = new BTalk.Timer.DisposableTimer("Sending configuration data"))
            {
                var msg = BitConverter.GetBytes(configurationPrefix).Concat(CryptoSystem.Encode(configuration)).ToArray();
                SendMessage(msg);
            }
        }

        public void SendFile(string fileName, byte[] data)
        {
            using (var timer = new BTalk.Timer.DisposableTimer("Sending file"))
            {
                var msg = BitConverter.GetBytes(filePrefix).Concat(CryptoSystem.Encode(data).ToArray()).ToArray();

                SendMessage(msg);
            }
        }

        public void Dispose()
        {
            Listener.Stop();
            foreach (var socket in ServerSockets)
            {
                socket.Close();
            }
            Client.Close();
        }

        private void SendMessage(byte[] msg)
        {
            if (Client != null)
            {
                BinaryWriter writer = new BinaryWriter(Client.GetStream());
                writer.Write(BitConverter.GetBytes(msg.Length).Concat(msg).ToArray());
                writer.Flush();
            }
            else
            {
                foreach (var socket in ServerSockets)
                {
                    BinaryWriter writer = new BinaryWriter(new NetworkStream(socket));
                    writer.Write(BitConverter.GetBytes(msg.Length).Concat(msg).ToArray());
                    writer.Flush();
                }
            }
        }

        public void SetCipher(Crypto.ICryptoStrategy algorithm, bool sendConfigurationData)
        {
            CryptoSystem.Algorithm = algorithm;
            sessioninitialized = !sendConfigurationData;
            if (!sessioninitialized)
            {
                if (algorithm is Rsa)
                {
                    var rsaAlgorithm = algorithm as Rsa;
                    var aesAlgorithm = rsaAlgorithm.SymmetricAlgorithm as Aes;
                    rsaAlgorithm.Mode = RsaMode.Normal;
                    SendConfigurationData(aesAlgorithm.AesCipher.Key);
                    rsaAlgorithm.Mode = RsaMode.Hybrid;
                }
                sessioninitialized = true;
            }
        }

        private void HandleMessage(NetworkStream stream)
        {
            if (stream.DataAvailable)
            {
                char type = 'z';
                byte[] data = null;

                using (var timer = new BTalk.Timer.DisposableTimer("Message revieved"))
                {
                    BinaryReader reader = new BinaryReader(stream);
                    int size = BitConverter.ToInt32(reader.ReadBytes(4), 0);
                    byte[] allBytes = reader.ReadBytes(size);
                    byte[] typeBytes = allBytes.Take(2).ToArray();
                    type = BitConverter.ToChar(typeBytes, 0);
                    data = CryptoSystem.Decode(allBytes.Skip(2).ToArray());
                }

                switch (type)
                {
                    case filePrefix:
                        FileReceived(data, "dummyfile");
                        return;
                    case messagePrefix:
                        string message = UTF8Encoding.UTF8.GetString(data);
                        MessageReceived(message);
                        return;
                    case configurationPrefix:
                        AlgorithmNegotiated(data, "Rsa");
                        break;
                }
            }
        }

    }
}
